package com.carkeeper.log;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @author duoduo
 * @version 1.0.0
 * @date Created in 2020/06/05
 * @description 创建访问日志全局过滤器,然后在此过滤器进行日志构造。
 */

@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {

    private AccessLogEntity accessLog = new AccessLogEntity();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        InetSocketAddress address = request.getRemoteAddress();
        HttpMethod method = request.getMethod();
        URI url = request.getURI();
        HttpHeaders headers = request.getHeaders();

        //获取请求体
        Flux<DataBuffer> body = exchange.getAttribute("cachedRequestBodyObject");
        StringBuilder sb = new StringBuilder();
        if (body != null) {
            body.subscribe(buffer -> {
                byte[] bytes = new byte[buffer.readableByteCount()];
                buffer.read(bytes);
                DataBufferUtils.release(buffer);
                String bodyString = new String(bytes, StandardCharsets.UTF_8);
                sb.append(bodyString);
            });
        }

        String requestParams = sb.toString();

        accessLog.setRequestHeaders(headers);
        accessLog.setRequestParams(requestParams);
        accessLog.setAddress(address.getHostName() +":"+ address.getPort());
        accessLog.setMethod(method.name());
        accessLog.setHost(url.getHost() + ":" +url.getPort());
        accessLog.setPath(url.getPath());

        ServerHttpResponse response = exchange.getResponse();

        DataBufferFactory bufferFactory = response.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        // probably should reuse buffers
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        String responseResult = new String(content, Charset.forName("UTF-8"));
                        accessLog.setStatusCode(this.getStatusCode().toString());
                        accessLog.setResponseHeaders(this.getHeaders());
                        accessLog.setResponseResult(responseResult);
                        log.info("accessLog : {}",accessLog);
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body); // if body is not a flux. never got there.
            }
        };

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }

}
